/**
 *  Return a SkImage using the encoded data, but attempts to defer decoding until the
 *  image is actually used/drawn. This deferral allows the system to cache the result, either on the
 *  CPU or on the GPU, depending on where the image is drawn. If memory is low, the cache may
 *  be purged, causing the next draw of the image to have to re-decode.
 *
 *  If alphaType is nullopt, the image's alpha type will be chosen automatically based on the
 *  image format. Transparent images will default to kPremul_SkAlphaType. If alphaType contains
 *  kPremul_SkAlphaType or kUnpremul_SkAlphaType, that alpha type will be used. Forcing opaque
 *  (passing kOpaque_SkAlphaType) is not allowed, and will return nullptr.
 *
 *  If the encoded format is not supported, nullptr is returned.
 *
 *  @param encoded  the encoded data
 *  @return         created SkImage, or nullptr

    example: https://fiddle.skia.org/c/@Image_DeferredFromEncodedData
*/


#include "src/image/SkImage_OH.h"

#include "include/core/SkBitmap.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPixelRef.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "include/core/SkTypes.h"
#include "include/oh/OHDrawingAPI.h"
#include "src/base/SkRectMemcpy.h"
#include "src/core/SkImageInfoPriv.h"
#include "src/core/SkImagePriv.h"
#include "src/core/SkTraceEvent.h"
#include "src/core/SkTraceEventCommon.h"
#include "src/image/SkImage_Base.h"

#include <cstddef>
#include <cstdint>
#include <stdatomic.h>
#include <utility>

class GrDirectContext;

// static void release_data(void* addr, void* context) {
//     SkData* data = static_cast<SkData*>(context);
//     data->unref();
// }

SkImage_OH::SkImage_OH(const SkImageInfo &info, OH_ImageSourceNative *source, int frameIndex, bool sourceNeedRelease,
                       float rotateDegree, bool needFlip, uint32_t id)
    : SkImage_Base(info, id) {
    fImageSource = source;
    fSourceNeedRelease = sourceNeedRelease;
    fFrameIndex = frameIndex;
    fRotateDegree = rotateDegree;
    fNeedFlip = needFlip;
    if (fImageSource) {
        buildPixelmap(info.width(), info.height(), fFrameIndex);
        tryRebuildPixelRef();
    }
    if (fPixelRef) {
        fHash = fPixelRef->hash();
    } else {
        fHash = 0;
    }
}

SkImage_OH::SkImage_OH(const SkImageInfo &info, OH_PixelmapNative *pixelmap, uint32_t id): SkImage_Base(info, id) {
    fDecodePixelmap = pixelmap;
    tryRebuildPixelRef();
}

SkImage_OH::~SkImage_OH() {
    if (fImageSource && fSourceNeedRelease) {
        OH_ImageSourceNative_Release(fImageSource);
    }
}

bool SkImage_OH::isValid(GrRecordingContext *context) const {
    if (!getOHPixelmap(imageInfo().width(), imageInfo().height())) {
        return false;
    } else {
        return true;
    }
}

bool SkImage_OH::getScalePixels(SkBitmap &dst, const SkSamplingOptions &option, CachingHint cachingHint) const {
//     if (option.mipmap != SkMipmapMode::kLinear) {
//     }
    // @todo: need find the situation where using pixelmap scale is better.
    return false;
    OH_PixelmapNative *pixelmap = nullptr;
    buildPixelmap(dst.width(), dst.height(), fFrameIndex, &pixelmap);
    if (pixelmap) {
        auto pixelref = sk_make_sp<SkPixelRef>(pixelmap);
        if (pixelref->width() != dst.width() || pixelref->height() != dst.height()) {
            SkDebugf("scale width not equal get: %d %d need: %d %d", pixelref->width(), pixelref->height(), dst.width(),
                     dst.height());
        }

        dst.setInfo(SkImageInfo::Make(pixelref->width(), pixelref->height(), dst.colorType(), dst.alphaType()));
        dst.setPixelRef(pixelref, 0, 0);
        return true;
    }
    return false;
}

bool SkImage_OH::onReadPixels(GrDirectContext *dContext, const SkImageInfo &dstInfo, void *dstPixels,
                              size_t dstRowBytes, int srcX, int srcY, CachingHint chint) const {
    SkBitmap bm;
    if (this->getROPixels(dContext, &bm, chint)) {
        return bm.readPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY);
    }
    return false;
}

bool SkImage_OH::getROPixels(GrDirectContext *, SkBitmap *dst, CachingHint) const {
    if (fPixelRef && (imageInfo().width() != fPixelRef->width() || imageInfo().height() != fPixelRef->height())) {
        SkDebugf("release old pixelmap to get diff pixelmap");
        fPixelRef.reset();
    }
    if (!fPixelRef) {
        buildPixelmap(imageInfo().width(), imageInfo().height(), fFrameIndex);
        tryRebuildPixelRef();
    }
    if (dst && fPixelRef) {
        dst->setInfo(imageInfo());
        dst->setPixelRef(fPixelRef, 0, 0);
        return true;
    } else {
        return false;
    }
}

sk_sp<SkImage> SkImage_OH::onMakeSubset(GrDirectContext *, const SkIRect &subset) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
}

sk_sp<SkImage> SkImage_OH::onMakeSubset(skgpu::graphite::Recorder *, const SkIRect &subset,
                                        RequiredProperties requiredProperties) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
}

///////////////////////////////////////////////////////////////////////////////

sk_sp<SkImage> SkImage_OH::onMakeColorTypeAndColorSpace(SkColorType targetCT, sk_sp<SkColorSpace> targetCS,
                                                        GrDirectContext *) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
}

sk_sp<SkImage> SkImage_OH::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
}

void SkImage_OH::tryRebuildPixelRef() const {
    auto pixelmap = fDecodePixelmap.exchange(nullptr);
    if (pixelmap) {
        fPixelRef = sk_make_sp<SkPixelRef>(pixelmap);
    }
    return;
}


void SkImage_OH::buildPixelmap(int width, int height, int index, OH_PixelmapNative **dst) const {
    if (fImageSource) {
        TRACE_EVENT0("skia", __FUNCTION__);
        OH_DecodingOptions *opts = nullptr;
        auto err_code = OH_DecodingOptions_Create(&opts);
        if (opts == nullptr || err_code != IMAGE_SUCCESS) {
            SkDebugf("Create DecodingOptions failed:%d", err_code);
            return;
        }

        auto info = imageInfo();
        Image_Size size = {(uint32_t)width, (uint32_t)height};
        OH_DecodingOptions_SetDesiredSize(opts, &size);
        OH_DecodingOptions_SetPixelFormat(opts, PIXEL_FORMAT_RGBA_8888);
        OH_DecodingOptions_SetRotate(opts, fRotateDegree);
        OH_DecodingOptions_SetDesiredDynamicRange(opts, IMAGE_DYNAMIC_RANGE_SDR);
        OH_DecodingOptions_SetIndex(opts, index);

        OH_PixelmapNative *pixelmap = nullptr;
        // This could be time-consuming.
//        err_code = OH_ImageSourceNative_CreatePixelmap(fImageSource, opts, &pixelmap);
        // By adopting the following creation method, we can save some memory without increasing GPU cycles.
        err_code = OHDrawingAPI::OH_ImageSourceNative_CreatePixelmapUsingAllocator(fImageSource, opts,
                                                                                   IMAGE_ALLOCATOR_TYPE_DMA, &pixelmap);

        if (fNeedFlip) {
            OH_PixelmapNative_Flip(pixelmap, fNeedFlip, false);
        }
        OH_DecodingOptions_Release(opts);

        if (dst) {
            *dst = pixelmap;
        } else {
            pixelmap = fDecodePixelmap.exchange(pixelmap);
            if (pixelmap) {
                OH_PixelmapNative_Release(pixelmap);
            }
        }
    }
}

OH_Drawing_PixelMap *SkImage_OH::getOHPixelmap(int width, int height) const {
    if (fPixelRef && (imageInfo().width() != fPixelRef->width() || imageInfo().height() != fPixelRef->height())) {
        SkDebugf("release old pixelmap to get diff pixelmap");
        fPixelRef.reset();
    }
    if (!fPixelRef && fImageSource) {
        buildPixelmap(width, height, fFrameIndex);
        tryRebuildPixelRef();
    }

    if (fPixelRef) {
        return fPixelRef->getDrawingPixelMap();
    } else {
        return nullptr;
    }
}

OH_Drawing_Image *SkImage_OH::getOHDrawingImage(int width, int height) const {
    if (!fPixelRef) {
        getOHPixelmap(width, height);
    }
    if (!fPixelRef) {
        return nullptr;
    }
    if (fPixelRef->getDrawingImage() || fPixelRef->buildDrawingImageOHOS(imageInfo())) {
        return fPixelRef->getDrawingImage();
    } else {
        return nullptr;
    }
}

uint64_t SkImage_OH::hash() const {
    if (!fPixelRef) {
        getOHPixelmap(imageInfo().width(), imageInfo().height());
    }
    if (fPixelRef) {
        return fPixelRef->hash();
    } else {
        return 0;
    }
}

static void ResolveEncodedOrigin(char *data, int size, float *rotate, bool *need_flip) {
    if (size == sizeof("Top-left") - 1 && memcmp("Top-left", data, size) == 0) {
        *rotate = 0.f;
        *need_flip = false;
    } else if (size == sizeof("Top-right") - 1 && memcmp("Top-right", data, size) == 0) {
        *rotate = 0.f;
        *need_flip = true;
    } else if (size == sizeof("Bottom-right") - 1 && memcmp("Bottom-right", data, size) == 0) {
        *rotate = 180.f;
        *need_flip = false;
    } else if (size == sizeof("Bottom-left") - 1 && memcmp("Bottom-left", data, size) == 0) {
        *rotate = 180.f;
        *need_flip = true;
    } else if (size == sizeof("Left-top") - 1 && memcmp("Left-top", data, size) == 0) {
        *rotate = 90.f;
        *need_flip = true;
    } else if (size == sizeof("Right-top") - 1 && memcmp("Right-top", data, size) == 0) {
        *rotate = 90.f;
        *need_flip = false;
    } else if (size == sizeof("Right-bottom") - 1 && memcmp("Right-bottom", data, size) == 0) {
        *rotate = 270.f;
        *need_flip = true;
    } else if (size == sizeof("Left-bottom") - 1 && memcmp("Left-bottom", data, size) == 0) {
        *rotate = 270.f;
        *need_flip = false;
    } else {
        *rotate = 0.f;
        *need_flip = false;
    }
}

static void GetRotateFlip(OH_ImageSourceNative *source, float &degree, bool &needFlip) {
    Image_String key;
    key.data = (char *)OHOS_IMAGE_PROPERTY_ORIENTATION;
    key.size = strlen(OHOS_IMAGE_PROPERTY_ORIENTATION);
    Image_String value = {nullptr, 0};
    int err = OH_ImageSourceNative_GetImageProperty(source, &key, &value);
    if (err != IMAGE_SUCCESS) {
        SkDebugf("cannot get pixelmap orientation: %d", err);
    }
    if (value.data != nullptr) {
        ResolveEncodedOrigin(value.data, value.size, &degree, &needFlip);
        free(value.data);
    }
}

static bool isJPEG(sk_sp<SkData> data) {
    return data != nullptr && data->size() > 2 && data->bytes()[0] == 0xFF && data->bytes()[1] == 0xD8;
}

sk_sp<SkImage> SkImages::DeferredFromEncodedData(sk_sp<SkData> encoded, std::optional<SkAlphaType> alphaType) {
    TRACE_EVENT0("skia", __FUNCTION__);
    OH_ImageSourceNative *image_source = nullptr;
    OH_ImageSource_Info *info = nullptr;
    OH_ImageSourceInfo_Create(&info);
    if (info == nullptr) {
        return nullptr;
    }

    // The data will be coyied to ImageSourceNative.
    // No modifications will be made to origin data.
    auto err_code = OH_ImageSourceNative_CreateFromData((uint8_t *)encoded->bytes(), encoded->size(), &image_source);
    if (image_source == nullptr || err_code != IMAGE_SUCCESS) {
        return nullptr;
    }

    uint32_t width = 0, height = 0;
    OH_ImageSourceNative_GetImageInfo(image_source, 0, info);
    OH_ImageSourceInfo_GetWidth(info, &width);
    OH_ImageSourceInfo_GetHeight(info, &height);
    OH_ImageSourceInfo_Release(info);
    if (width == 0 || height == 0) {
        OH_ImageSourceNative_Release(image_source);
        return nullptr;
    }

    float rotateDegree = 0.f;
    bool needFlip = false;
    GetRotateFlip(image_source, rotateDegree, needFlip);
    
    SkAlphaType alpha = kUnpremul_SkAlphaType;
    if (isJPEG(encoded)) {
        alpha = kOpaque_SkAlphaType;
    }

    auto sk_info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, alpha);
    if (rotateDegree == 90.f || rotateDegree == 270.f) {
        sk_info = SkImageInfo::Make(height, width, kRGBA_8888_SkColorType, alpha);
    }

    return sk_make_sp<SkImage_OH>(sk_info, image_source, rotateDegree, needFlip);
}


sk_sp<SkImage> SkImages::DeferredFromOHPixelmap(OH_PixelmapNative *pixelmap) {
    if (!pixelmap) {
        return nullptr;
    }
    OH_Pixelmap_ImageInfo *real_info = nullptr;
    OH_PixelmapImageInfo_Create(&real_info);
    OH_PixelmapNative_GetImageInfo(pixelmap, real_info);

    uint32_t real_width = 0, real_height = 0, real_rowBytes = 0;
    int32_t pixel_format, alpha_type;
    OH_PixelmapImageInfo_GetWidth(real_info, &real_width);
    OH_PixelmapImageInfo_GetHeight(real_info, &real_height);
    OH_PixelmapImageInfo_GetRowStride(real_info, &real_rowBytes);
//    OH_PixelmapImageInfo_GetPixelFormat(real_info, &pixel_format);
//    OH_PixelmapImageInfo_GetAlphaType(real_info, &alpha_type);
    
    OH_PixelmapImageInfo_Release(real_info);
    auto sk_info = SkImageInfo::Make(real_width, real_height, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
    
    
    return sk_make_sp<SkImage_OH>(sk_info, pixelmap);
    
}